/*
** DirectX pieces for GSP260
**
** Revision: $Id: Renderer.cpp 17 2010-09-11 01:25:07Z Tim Brengle $
*/
#include "Renderer.h"
#include <dxerr.h>


/*****************************************************\
* Global variables: vectors here for easy changing    *
\*****************************************************/
const D3DXVECTOR3 cvViewPosition(0.0f, 0.0f, 1000.0f);		// eye (or camera) position
const D3DXVECTOR3 cvLightDirection(-.50f, -1.0f, -2.0f);	// direction light shines
const D3DXVECTOR3 cvTarget(0.0f, 0.0f, 0.0f);				// object position in world coordinates
const D3DXVECTOR3 cvUp(0.0f, 1.0f, 0.0f);					// Up direction


/*****************************************************\
* HR macro for error handling                         *
\*****************************************************/
#ifdef _DEBUG
	#ifndef HR
	#define HR(x)					\
	{								\
		HRESULT hr = x;				\
		if (FAILED(hr))				\
		{							\
			DXTRACE_ERR(#x, hr);	\
			DebugBreak();			\
		}							\
	}
	#endif
#else
	#ifndef HR
	#define HR(x) x;
	#endif
#endif 



/*****************************************************\
* Renderer constructor                                *
\*****************************************************/
Renderer::Renderer()
  : m_pD3DDevice(NULL)
  , m_hWnd(NULL)
  , m_fScreenWidth(0)
  , m_fScreenHeight(0)
  , m_eFillMode(D3DFILL_SOLID)
  , m_eShadeMode(D3DSHADE_PHONG)
  , m_vViewPoint(cvViewPosition)
  , m_bShowTextures(true)
  , m_bSpinning(true)
  , m_bMaterialDemo(false)
  , m_bShowAmbient(true)
  , m_bShowDiffuse(true)
  , m_bShowSpecular(true)
{
}


/*****************************************************\
* Destructor                                          *
\*****************************************************/
Renderer::~Renderer()
{
	Cleanup();
}


/*****************************************************\
* InitializeD3D                                       *
\*****************************************************/
void Renderer::InitializeD3D(HWND hWnd)
{
	/* Can't start D3D without a window to draw upon */
	if (hWnd == NULL)
	{
		return;
	}
	m_hWnd = hWnd;

	/* Set screen width and height to client rectangle */
	RECT rClientRect;
	if (! ::GetClientRect(m_hWnd, &rClientRect))
	{
		/* Can't get the size of the drawable area: a Bad Thing */
		throw "GetClientRect() failed";
	}
	m_fScreenWidth = (float)(rClientRect.right - rClientRect.left);
	m_fScreenHeight = (float)(rClientRect.bottom - rClientRect.top);

	/* Create D3D object */
	IDirect3D9* pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (NULL == pD3D)
	{
		throw "Direct3DCreate9() failed";
	}

	/* Find out about default display mode */
	D3DDISPLAYMODE DisplayMode;
	HR(pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &DisplayMode));

	/* Find out about formats for that display mode */
	HR(pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, DisplayMode.Format,
		D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16));

	/* Find out the video card's capabilities */
	D3DCAPS9 D3DCaps;
	HR(pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));

	/* Check device capabilities against requirements */
	DWORD dwBehaviorFlags = 0;
	/* Use hardware vertex processing if supported otherwise default to software */
	if (D3DCaps.VertexProcessingCaps != 0)
	{
		dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	}
	else
	{
		dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}

	/* All system checks passed, create the D3D device */
	memset(&m_PresentParams, 0, sizeof(m_PresentParams));
	m_PresentParams.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	m_PresentParams.hDeviceWindow			= m_hWnd;
	m_PresentParams.BackBufferFormat		= DisplayMode.Format;
	m_PresentParams.Windowed				= TRUE;
	m_PresentParams.PresentationInterval	= D3DPRESENT_INTERVAL_ONE;
	m_PresentParams.BackBufferWidth			= (int)m_fScreenWidth;
	m_PresentParams.BackBufferHeight		= (int)m_fScreenHeight;
	m_PresentParams.EnableAutoDepthStencil	= TRUE;
	m_PresentParams.AutoDepthStencilFormat	= D3DFMT_D16;

	HR(pD3D->CreateDevice(D3DADAPTER_DEFAULT,
						  D3DDEVTYPE_HAL,
						  m_hWnd,
						  dwBehaviorFlags,
						  &m_PresentParams,
						  &m_pD3DDevice));

	/* Delease D3D object since No longer needed. Only need D3D device from here on.  */
	pD3D->Release();

	/*
	** D3D is initialized.  Prepare some things for our scene
	*/
	CreateLight();
	CreateMaterials();

	/*
	** Create PERSPECTIVE transform
	**		which defines how 3D depth looks
	**
	**	(Does not change during program)
	*/
	D3DXMatrixPerspectiveFovLH(&m_matProj, (D3DX_PI / 4), (m_fScreenWidth / m_fScreenHeight), 1.0f, 1600.0f);
	HR(m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &m_matProj));

	/*
	** Create VIEW transform
	**		which defines the position and orientation of the view point (or "camera")
	**
	**	(Changes when eye location changes)
	*/
	D3DXMatrixLookAtLH(&m_matView, &m_vViewPoint, &cvTarget, &cvUp);
	HR(m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_matView));

	/* Set permanent render states */
	HR(m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE));			// turn on the 3D lighting
	HR(m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE));			// turn on the z-buffer
	HR(m_pD3DDevice->SetRenderState(D3DRS_AMBIENT,
									D3DCOLOR_XRGB(50, 50, 50)));	// ambient light

	/* Set initial values for render states which change */
	HR(m_pD3DDevice->SetRenderState(D3DRS_SHADEMODE, m_eShadeMode));// initial values for shading
	HR(m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, m_eFillMode));	// and filling
}


/*****************************************************\
* Cleanup                                             *
\*****************************************************/
void Renderer::Cleanup()
{
	if (NULL != m_pD3DDevice)
	{
		m_pD3DDevice->Release();
	}
}


/*****************************************************\
* LoadTextureFile                                     *
\*****************************************************/
void Renderer::LoadTextureFile(const char* cszTextureFile, IDirect3DTexture9*& pTexture)
{
	HR(D3DXCreateTextureFromFile(m_pD3DDevice, cszTextureFile, &pTexture));
}


/*****************************************************\
* LoadMeshFile                                        *
\*****************************************************/
void Renderer::LoadMeshFile(const char* cszXFile, ID3DXMesh*& pMesh)
{
	/* Empty the mesh if it is not empty */
	if (NULL != pMesh)
	{
		pMesh->Release();
	}
	pMesh = NULL;

	/* Prepare buffers returned by D3DXLoadMeshFromX */
	ID3DXBuffer* pAdjacency = NULL;			// required for OptimizeInPlace
	ID3DXBuffer* pMaterials = NULL;			// materials for each subset
	ID3DXBuffer* pEffectsInstances = NULL;	// not interested in effects now
	DWORD dwNumMaterials = 0;

	/* Load the file into the mesh */
	HR(D3DXLoadMeshFromX(cszXFile, D3DXMESH_MANAGED, m_pD3DDevice, &pAdjacency, &pMaterials, &pEffectsInstances, &dwNumMaterials, &pMesh));
	DWORD dError = ::GetLastError();

	/* What is the flexible vertex format of the vertex buffer before cloning? */
	DWORD dMeshFVF = pMesh->GetFVF();

	/* Clone the mesh, ensuring that it has room for position, normals, and texture coordinates */
	ID3DXMesh* pClone = NULL;
	HR(pMesh->CloneMeshFVF(D3DPOOL_MANAGED, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2, m_pD3DDevice, &pClone));
	pMesh->Release();
	pMesh = pClone;

	/* Recompute the normals if they were not in the file */
	if (0 == (dMeshFVF & D3DFVF_NORMAL))
	{
		HR(D3DXComputeNormals(pMesh, (DWORD*)pAdjacency->GetBufferPointer()));
	}

	/* Optimize the mesh */
	HR(pMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
							  (DWORD*)pAdjacency->GetBufferPointer(),
							  0, 0, 0));

	/* Done with buffers */
	pAdjacency->Release();
	pEffectsInstances->Release();
	pMaterials->Release();
}


/*****************************************************\
* CreateLight                                      *
\*****************************************************/
void Renderer::CreateLight()
{
	/* Define a directional light */
	D3DLIGHT9 Light;
	D3DXVECTOR3 LightPos(0.0f, 100.0f, 0.0f);
	ZeroMemory(&Light, sizeof(D3DLIGHT9));

	Light.Position = LightPos;

	Light.Type = D3DLIGHT_DIRECTIONAL;
	Light.Diffuse = D3DXCOLOR(1, 1, 1, 1);	// color of the light
	Light.Specular = D3DXCOLOR(1, 1, 1, 1);
	D3DXVec3Normalize((D3DXVECTOR3*)&Light.Direction, &cvLightDirection);
	Light.Range = 1000;
	Light.Falloff = 0;
	Light.Attenuation0 = 1;
	Light.Attenuation1 = 0;
	Light.Attenuation2 = 0;

	m_pD3DDevice->SetLight(0, &Light);    // send the light struct properties to light #0
	m_pD3DDevice->LightEnable(0, TRUE);   // turn on light #0
	m_pD3DDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);
}


/*****************************************************\
* CreateMaterials                                     *
\*****************************************************/
void Renderer::CreateMaterials()
{
	const D3DXCOLOR OFF = D3DXCOLOR(0,0,0,0);

	ZeroMemory(&m_cDefaultMaterial, sizeof(D3DMATERIAL9));
	m_cDefaultMaterial.Ambient = (! m_bShowAmbient) ? OFF : D3DXCOLOR(1, 1, 1, 1);	// set diffuse color to white
	m_cDefaultMaterial.Diffuse = (! m_bShowDiffuse) ? OFF : D3DXCOLOR(1, 1, 1, 1);		// set ambient color to match diffuse
	m_cDefaultMaterial.Specular = (! m_bShowSpecular) ? OFF : D3DXCOLOR(1, 1, 1, 1);	// set specular color
	m_cDefaultMaterial.Power = 100;

	ZeroMemory(&m_cDemoMaterial, sizeof(D3DMATERIAL9));
	m_cDemoMaterial.Ambient = (! m_bShowAmbient) ? OFF : D3DXCOLOR(1, 0, 1, 1);	// set ambient color to red
	m_cDemoMaterial.Diffuse = (! m_bShowDiffuse) ? OFF : D3DXCOLOR(0, 1, 0, 1);	// set diffuse color to green
	m_cDemoMaterial.Specular = (! m_bShowSpecular) ? OFF : D3DXCOLOR(1, .5f, 0, 1);	// set specular color to blue
	m_cDemoMaterial.Power = 20;

	m_pD3DDevice->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL);
}


/*****************************************************\
* SetViewPoint                                        *
\*****************************************************/
void Renderer::SetViewPoint(D3DXVECTOR3& vNew)
{
	m_vViewPoint = vNew;

	/*
	** Reset VIEW matrix:
	**		Create the view transform,
	**		which defines the position and orientation of the view point (or "camera")
	*/
	D3DXMatrixLookAtLH(&m_matView, &m_vViewPoint, &cvTarget, &cvUp);
	HR(m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_matView));
}


/*****************************************************\
* RenderOneFrame                                      *
\*****************************************************/
void Renderer::RenderOneFrame(ObjectList& objects)
{
	/*
	** Watch for our window being covered, minimized or otherwise deactivated
	*/
	/* if we have lost our D3DDevice pointer, just return */
	if (NULL == m_pD3DDevice)
	{
		return;
	}
	/* Check for lost device */
	HRESULT hDevState = m_pD3DDevice->TestCooperativeLevel();
	switch (hDevState)
	{
		case D3DERR_DEVICELOST:
			/* if the device is lost, return and check it again next frame */
			::OutputDebugString("Device lost");
			::Sleep(100);
			return;

		case D3DERR_DEVICENOTRESET:
			/* Finally have waited long enough; it is time to reset the D3D device */
			if (FAILED(m_pD3DDevice->Reset(&m_PresentParams)))
			{
				::OutputDebugString("Reset failed");
				return;
			}
			break;
	}
	/* Done checking for deactivation and lost devices; all is well */

	/*
	** ROTATION matrix and WORLD matrix:
	**
	**	Will combine to form final WORLD transform,
	**	which defines how to change from the object's local
	**	coordinate system to the world coordinate system.
	*/
	D3DXMATRIX matWorld;
	D3DXMATRIX matRotation;

	/* Clear the back buffer and the z-buffer */
	HR(m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(248, 216, 194), 1.0f, 0));

	/*
	** Begin the scene:
	**		start rendering everything to the back buffer
	*/
	HRESULT hr = m_pD3DDevice->BeginScene();
	if (FAILED(hr))
	{
		/* Stop rendering if BeginScene fails */
		return;
	}

	/* Draw the objects */
	for each (Object* pObject in objects)
	{
		pObject->SetSpinning(m_bSpinning);

		//D3DXMatrixTranslation(&matWorld,
		//					  pObject->m_vPosition.x, pObject->m_vPosition.y, pObject->m_vPosition.z);

		///*
		//** Use the world transform to rotate the object in the 3D world.
		//*/
		//D3DXMatrixRotationY(&matRotation, pObject->m_fAngle);
		//D3DXMatrixMultiply(&matWorld, &matRotation, &matWorld);

		/* Tell D3D where to draw the object */
		HR(m_pD3DDevice->SetTransform(D3DTS_WORLD, &pObject->m_mPosition));

		/* Which material? */
		m_pD3DDevice->SetMaterial((m_bMaterialDemo) ? &m_cDemoMaterial : &m_cDefaultMaterial);

		/* Set the texture */
		m_pD3DDevice->SetTexture(0, pObject->m_pTexture);
		/* Enable/disable texture display */
		m_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, (m_bShowTextures) ? D3DTOP_MODULATE : D3DTOP_DISABLE);

		/* Draw the object */
		pObject->m_pMesh->DrawSubset(pObject->m_dwSubset);
	}

	/*
	** End the scene; drawing is all done
	*/
	hr = m_pD3DDevice->EndScene();
    if (FAILED(hr))
    {
		/* Stop rendering if EndScene fails */
        return;
    }

	/*
	** Display the back buffer on the screen
	*/
	m_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}


/*****************************************************\
* SetShadeMode                                       *
\*****************************************************/
void Renderer::SetShadingMode(SHADE_MODE eMode)
{
	m_eShadeMode = (D3DSHADEMODE)eMode;
	HR(m_pD3DDevice->SetRenderState(D3DRS_SHADEMODE, m_eShadeMode));
}


/*****************************************************\
* SetFillMode                                       *
\*****************************************************/
void Renderer::SetFillMode(FILL_MODE eMode)
{
	m_eFillMode = (D3DFILLMODE)eMode;
	HR(m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, m_eFillMode));
}

void Renderer::ToggleLightKind(LIGHT_KIND l)
{
	switch (l)
	{
		case AMBIENT:
			m_bShowAmbient = ! m_bShowAmbient;
			break;

		case DIFFUSE:
			m_bShowDiffuse = ! m_bShowDiffuse;
			break;

		case SPECULAR:
			m_bShowSpecular = ! m_bShowSpecular;
			break;
	}

	/* Remake the materials */
	CreateMaterials();
}
